home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue64 / Alfresco / AASorter.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  2000-10-23  |  20.4 KB  |  644 lines

  1. {*********************************************************}
  2. {* AASorter                                              *}
  3. {* Copyright (c) Julian M Bucknall 2000                  *}
  4. {* All rights reserved.                                  *}
  5. {*********************************************************}
  6. {* Algorithms Alfresco: Sorter class                     *}
  7. {*********************************************************}
  8.  
  9. {Note: this unit is released as freeware. In other words, you are free
  10.        to use this unit in your own applications, however I retain all
  11.        copyright to the code. JMB}
  12.  
  13. unit AASorter;
  14.  
  15. interface
  16.  
  17. uses
  18.   Windows,
  19.   SysUtils,
  20.   Classes;
  21.  
  22. type
  23.   {function prototype to compare two items;
  24.    returns integer <0 if Item1<Item2, =0 if equal, >0 otherwise}
  25.   TaaMergeCompare = function (const aItem1, aItem2 : pointer) : integer;
  26.  
  27. type
  28.   TaaSorter = class
  29.     private
  30.       FBuffer      : PChar;
  31.       FCompare     : TaaMergeCompare;
  32.       FCurRec      : integer;
  33.       FDestFile    : TObject;
  34.       FF1          : TObject;
  35.       FF2          : TObject;
  36.       FG1          : TObject;
  37.       FG2          : TObject;
  38.       FMaxRecCount : integer;
  39.       FRecCount    : integer;
  40.       FRecLen      : integer;
  41.       FSrcFile     : TObject;
  42.       FState       : integer;
  43.     protected
  44.       procedure srSetCompare(aValue : TaaMergeCompare);
  45.       procedure srMaxRecCount(aValue : integer);
  46.       procedure srSetRecLen(aValue : integer);
  47.  
  48.       procedure srCreateMergeFiles;
  49.       procedure srGetBuffer;
  50.       procedure srMergeFiles;
  51.       procedure srQuickSortBuffer;
  52.     public
  53.       constructor Create;
  54.       destructor Destroy; override;
  55.  
  56.       procedure Add(var aRecord);
  57.         {add a new record to the sorter}
  58.       function Get(var aRecord) : boolean;
  59.         {get a record from the sorter in sequence; returns true if a
  60.          record was retrieved, false if all records have been
  61.          obtained}
  62.       procedure Reset;
  63.         {reset the sorter to return to the "adding records" stage; all
  64.          records in the sorter are discarded}
  65.  
  66.       property Compare : TaaMergeCompare
  67.                   read FCompare write srSetCompare;
  68.         {the comparison function}
  69.       property MaxRecordCount : integer
  70.                   read FMaxRecCount write srMaxRecCount;
  71.         {maximum number of records to be held by the sorter before
  72.          writing them to disk and performing file merges}
  73.       property RecordLength : integer
  74.                   read FRecLen write srSetRecLen;
  75.         {length of the records begin added}
  76.   end;
  77.  
  78. implementation
  79.  
  80. const                     {internal states for the sorter}
  81.   WaitingState      = 0;  {empty, waiting for the first record}
  82.   AddingState       = 1;  {adding records, no flush to disk yet}
  83.   AddWithMergeState = 2;  {adding records, will require file merge}
  84.   GettingState      = 3;  {getting records}
  85.   GetWithMergeState = 4;  {getting records from merged file}
  86.   FinishedState     = 5;  {all records have been retrieved}
  87.  
  88. {===Quicksort========================================================}
  89. procedure QuickSort(aBuffer  : PChar;
  90.                     aRecLen  : integer;
  91.                     aCount   : integer;
  92.                     aCompare : TaaMergeCompare);
  93. var
  94.   Temp   : pointer;
  95.   {------}
  96.   function Partition(L, R : integer): integer;
  97.   var
  98.     Left : PChar;
  99.     Right: PChar;
  100.     Last : PChar;
  101.     First: PChar;
  102.   begin
  103.     {set up the indexes}
  104.     Left := aBuffer + (L * aRecLen);
  105.     First := Left;
  106.     Right := aBuffer + (pred(R) * aRecLen);
  107.     {get the partition element}
  108.     Last := Right + aRecLen;
  109.     {do forever (we'll break out of the loop when needed)}
  110.     while true do begin
  111.       {find the first element greater than or equal to the partition
  112.        element from the left; note that our partition element will
  113.        stop this loop}
  114.       while (aCompare(Left, Last) < 0) do
  115.         inc(Left, aRecLen);
  116.       {find the first element less than the partition element from the
  117.        right; check to break out of the loop if we hit the left
  118.        element - we have no sentinel there}
  119.       while (aCompare(Last, Right) < 0) do begin
  120.         if (Right = First) then
  121.           Break;
  122.         dec(Right, aRecLen);
  123.       end;
  124.       {if we crossed get out of this infinite loop to swap the
  125.        partition element into place}
  126.       if (Left >= Right) then
  127.         Break;
  128.       {otherwise swap the two out-of-place elements}
  129.       Move(Left^, Temp^, aRecLen);
  130.       Move(Right^, Left^, aRecLen);
  131.       Move(Temp^, Right^, aRecLen);
  132.       {and continue}
  133.       inc(Left, aRecLen);
  134.       dec(Right, aRecLen);
  135.     end;
  136.     {swap the partition element into place, return the dividing index}
  137.     Move(Left^, Temp^, aRecLen);
  138.     Move(Last^, Left^, aRecLen);
  139.     Move(Temp^, Last^, aRecLen);
  140.     Result := (Left - aBuffer) div aRecLen;
  141.   end;
  142.   {------}
  143.   procedure QuickSortPrim(L, R : integer);
  144.   var
  145.     DividingItem : integer;
  146.   begin
  147.     {stop the recursion, if needed}
  148.     if (R <= L) then
  149.       Exit;
  150.     {otherwise, partition about the final element in the set}
  151.     DividingItem := Partition(L, R);
  152.     {recursively quicksort the two subsets either side of the dividing
  153.      element}
  154.     QuicksortPrim(L, pred(DividingItem));
  155.     QuicksortPrim(succ(DividingItem), R);
  156.   end;
  157.   {------}
  158. begin
  159.   GetMem(Temp, aRecLen);
  160.   try
  161.     QuickSortPrim(0, pred(aCount));
  162.   finally
  163.     FreeMem(Temp);
  164.   end;
  165. end;
  166. {====================================================================}
  167.  
  168.  
  169. {===Mergesort=================================================================}
  170. function ReadRecFixed(aStream : TStream;
  171.                   var aBuffer;
  172.                       aRecLen : integer) : boolean;
  173. var
  174.   BytesRead : longint;
  175. begin
  176.   BytesRead := aStream.Read(aBuffer, aRecLen);
  177.   Result := BytesRead = aRecLen;
  178. end;
  179. {--------}
  180. function MergeRunsFixed(aF1     : TStream;
  181.                         aF2     : TStream;
  182.                         aG1     : TStream;
  183.                         aG2     : TStream;
  184.                         aRecLen : integer;
  185.                         aRunLen : integer;
  186.                         aCompare: TaaMergeCompare) : boolean;
  187. const
  188.   FirstFile = false;
  189.   SecondFile = true;
  190. type
  191.   {a record that describes the processing of a single input file}
  192.   TInputFile = packed record
  193.     ifStrm      : TStream; {stream}
  194.     ifRec       : pointer; {record buffer}
  195.     ifRecsInRun : integer; {records to go in run}
  196.     ifEOF       : boolean; {stream is exhausted}
  197.   end;
  198. var
  199.   F : array[boolean] of TInputFile;
  200.   G : array [boolean] of TStream;
  201.   SrcFile   : boolean;
  202.   DestFile  : boolean;
  203.   FileId    : boolean;
  204. begin
  205.   {assume that this merge pass will finish completely}
  206.   Result := true;
  207.   {initialize the input file records}
  208.   with F[FirstFile] do begin
  209.     ifStrm := aF1;
  210.     ifRec := nil;
  211.     ifRecsInRun := 0;
  212.     ifEOF := false;
  213.   end;
  214.   with F[SecondFile] do begin
  215.     ifStrm := aF2;
  216.     ifRec := nil;
  217.     ifRecsInRun := 0;
  218.     ifEOF := false;
  219.   end;
  220.   {set up the output files}
  221.   G[FirstFile] := aG1;
  222.   G[SecondFile] := aG2;
  223.   try
  224.     {clear the output streams}
  225.     {NOTE: this only works for Delphi 3 and above, since only
  226.            their TStreams have a SetSize accessor method}
  227.     G[FirstFile].Size := 0;
  228.     G[SecondFile].Size := 0;
  229.     {reset the input streams, allocate the record buffers,
  230.      and set the EOF flags}
  231.     for FileId := FirstFile to SecondFile do
  232.       with F[FileId] do begin
  233.         ifStrm.Seek(0, soFromBeginning);
  234.         GetMem(ifRec, aRecLen);
  235.         ifEOF := ifStrm.Size = 0;
  236.       end;
  237.     {make sure the first output goes to G1}
  238.     DestFile := FirstFile;
  239.     {cycle until we manage to exhaust both input files}
  240.     while (not F[FirstFile].ifEOF) or
  241.           (not F[SecondFile].ifEOF) do begin
  242.       {if we start writing to the second file, we won't finish
  243.        the merge process this time}
  244.       if (DestFile = SecondFile) then
  245.         Result := false;
  246.       {initialize ready for merging next runs}
  247.       F[FirstFile].ifRecsInRun := aRunLen;
  248.       F[SecondFile].ifRecsInRun := aRunLen;
  249.       {read the first two records in the respective runs}
  250.       with F[FirstFile] do
  251.         if ReadRecFixed(ifStrm, ifRec^, aRecLen) then
  252.           dec(ifRecsInRun)
  253.         else begin
  254.           ifRecsInRun := -1;
  255.           ifEOF := true;
  256.         end;
  257.       with F[SecondFile] do
  258.         if ReadRecFixed(ifStrm, ifRec^, aRecLen) then
  259.           dec(ifRecsInRun)
  260.         else begin
  261.           ifRecsInRun := -1;
  262.           ifEOF := true;
  263.         end;
  264.       {merge the two runs--one from F1 and the other from F2}
  265.       while ((F[FirstFile].ifRecsInRun >= 0) or
  266.              (F[SecondFile].ifRecsInRun >= 0)) do begin
  267.         {find the smaller record of the two current ones}
  268.         {if the run from F1 is exhausted then the record from
  269.          F2 is the 'smaller'}
  270.         if (F[FirstFile].ifRecsInRun < 0) then
  271.           SrcFile := SecondFile
  272.         {if the run from F2 is exhausted then the record from
  273.          F1 is the 'smaller'}
  274.         else if (F[SecondFile].ifRecsInRun < 0) then
  275.           SrcFile := FirstFile
  276.         {otherwise we need to actually compare the records to
  277.          find the smaller}
  278.         else
  279.           SrcFile :=
  280.              aCompare(F[FirstFile].ifRec, F[SecondFile].ifRec) > 0;
  281.         {write the smaller record to the current output file}
  282.         G[DestFile].WriteBuffer(F[SrcFile].ifRec^, aRecLen);
  283.         {read the next record from the file whose record we just used}
  284.         with F[SrcFile] do
  285.           if (ifRecsInRun <= 0) then
  286.             ifRecsInRun := -1
  287.           else if ReadRecFixed(ifStrm, ifRec^, aRecLen) then
  288.             dec(ifRecsInRun)
  289.           else begin
  290.             ifRecsInRun := -1;
  291.             ifEOF := true;
  292.           end
  293.       end;
  294.       {having merged two runs, switch output files}
  295.       DestFile := not DestFile;
  296.     end;
  297.   finally
  298.     if (F[SecondFile].ifRec <> nil) then
  299.       FreeMem(F[SecondFile].ifRec);
  300.     if (F[FirstFile].ifRec <> nil) then
  301.       FreeMem(F[FirstFile].ifRec);
  302.   end;
  303. end;
  304. {--------}
  305. function MergesortFixed(aF1 : TFileStream;
  306.                         aF2 : TFileStream;
  307.                         aG1 : TFileStream;
  308.                         aG2 : TFileStream;
  309.                         aRecLen  : integer;
  310.                         aRunLen  : integer;
  311.                         aCompare : TaaMergeCompare) : boolean;
  312. var
  313.   Merged : boolean;
  314.   FIsSrc : boolean;
  315. begin
  316.   {perform the first merge pass}
  317.   Merged := MergeRunsFixed(aF1, aF2, aG1, aG2,
  318.                            aRecLen, aRunLen, aCompare);
  319.   {now we continually merge the runs until we end up with a single
  320.    file containing all the records}
  321.   FIsSrc := true;
  322.   while not Merged do begin
  323.     aRunLen := aRunLen * 2;
  324.     FIsSrc := not FIsSrc;
  325.     if FIsSrc then
  326.       Merged := MergeRunsFixed(aF1, aF2, aG1, aG2,
  327.                                aRecLen, aRunLen, aCompare)
  328.     else
  329.       Merged := MergeRunsFixed(aG1, aG2, aF1, aF2,
  330.                                aRecLen, aRunLen, aCompare);
  331.   end;
  332.   {we've now merged all the records into either F1 or G1; return true
  333.    if it's F1, false if it's G1}
  334.   Result := not FIsSrc;
  335. end;
  336. {====================================================================}
  337.  
  338.  
  339. {===TaaTempFileStream================================================}
  340. type
  341.   TaaTempFileStream = class(TFileStream)
  342.     private
  343.       FFileName : string;
  344.       FDelete   : boolean;
  345.     protected
  346.     public
  347.       constructor Create(const aPath : string; aMode : word);
  348.       destructor Destroy; override;
  349.  
  350.       property DeleteOnDestroy : boolean read FDelete write FDelete;
  351.       property FileName : string read FFileName;
  352.   end;
  353. {--------}
  354. constructor TaaTempFileStream.Create(const aPath : string; aMode : word);
  355. var
  356.   PathNameZ : array [0..MAX_PATH] of char;
  357.   FileNameZ : array [0..MAX_PATH] of char;
  358. begin
  359.   {get the path for temporary files}
  360.   if (aPath = '') then
  361.     GetTempPath(sizeof(PathNameZ), PathNameZ)
  362.   else
  363.     StrLCopy(PathNameZ, PChar(aPath), sizeof(PathNameZ));
  364.   {create a temporary file}
  365.   GetTempFileName(PathNameZ, 'AA', 0, FileNameZ);
  366.   FFileName := FileNameZ;
  367.   {this last step will have created the file, so open it}
  368.   inherited Create(FileName, aMode);
  369. end;
  370. {--------}
  371. destructor TaaTempFileStream.Destroy;
  372. begin
  373.   {close the file}
  374.   inherited Destroy;
  375.   {if we're asked to delete the file, do so}
  376.   if DeleteOnDestroy then
  377.     DeleteFile(FileName);
  378. end;
  379. {====================================================================}
  380.  
  381.  
  382. {===TaaSorter========================================================}
  383. constructor TaaSorter.Create;
  384. begin
  385.   inherited Create;
  386. end;
  387. {--------}
  388. destructor TaaSorter.Destroy;
  389. begin
  390.   Reset;
  391.   RecordLength := 0;
  392.   inherited Destroy;
  393. end;
  394. {--------}
  395. procedure TaaSorter.Add(var aRecord);
  396. begin
  397.   {the add method can only be called if we're not in the middle of
  398.    getting records}
  399.   Assert((FState <> GettingState) and (FState <> GetWithMergeState) ,
  400.          'cannot add new records whilst in the process of getting them; call Reset first');
  401.  
  402.   {there's no point in adding records if we have no comparison method}
  403.   Assert(Assigned(FCompare),
  404.          'Sorter has no comparison method'); 
  405.  
  406.   {if we've no buffer, allocate one}
  407.   if (FBuffer = nil) then
  408.     srGetBuffer;
  409.  
  410.   {check to see whether we've filled the buffer}
  411.   if (FRecCount = FMaxRecCount) then begin
  412.     {if this was the first time that we filled the buffer, create the
  413.      merge files}
  414.     if (FState = AddingState) then
  415.       srCreateMergeFiles;
  416.     {sort then copy this bufferful of records to the correct Fx file}
  417.     srQuicksortBuffer;
  418.     TFileStream(FDestFile).WriteBuffer(FBuffer^, FRecLen * FRecCount);
  419.     {change the destination file for the next one}
  420.     if (FDestFile = FF1) then
  421.       FDestFile := FF2
  422.     else
  423.       FDestFile := FF1;
  424.     {reset the buffer}
  425.     FRecCount := 0;
  426.     {make sure the state is correct}
  427.     FState := AddWithMergeState;
  428.   end;
  429.  
  430.   {add this record to the buffer}
  431.   Move(aRecord, FBuffer[FRecLen * FRecCount], FRecLen);
  432.   inc(FRecCount);
  433.  
  434.   {make sure the state is correct}
  435.   if ((FState = WaitingState) or (FState = FinishedState)) then
  436.     FState := AddingState;
  437. end;
  438. {--------}
  439. function TaaSorter.Get(var aRecord) : boolean;
  440. var
  441.   BytesRead : integer;
  442. begin
  443.   {the get method can only be called if the sorter is not waiting for
  444.    records to be added}
  445.   Assert((FState <> WaitingState),
  446.          'cannot get new records if no records have yet been added');
  447.  
  448.   {get rid of the simple case}
  449.   if (FState = FinishedState) then begin
  450.     Result := false;
  451.     Exit;
  452.   end;
  453.  
  454.   {if the state is "adding records" then we need to quicksort the
  455.    buffer and change the state to "getting records"}
  456.   if (FState = AddingState) then begin
  457.     srQuicksortBuffer;
  458.     FCurRec := 0;
  459.     FState := GettingState;
  460.   end;
  461.  
  462.   {if the state is "adding records using mergefile" then we need to
  463.    write out the final buffer to the correct destination file, and
  464.    merge the files. The state gets changed to "getting records with
  465.    merge"}
  466.   if (FState = AddWithMergeState) then begin
  467.     srQuicksortBuffer;
  468.     TFileStream(FDestFile).WriteBuffer(FBuffer^, FRecLen * FRecCount);
  469.     srMergeFiles;
  470.     FCurRec := 0;
  471.     FRecCount := 0;
  472.     FState := GetWithMergeState;
  473.   end;
  474.  
  475.   Assert((FState = GettingState) or (FState = GetWithMergeState),
  476.          'The sorter state is incorrect half way through the Get method');
  477.  
  478.   {if the state is "getting records" return the next one in the
  479.    buffer; if there is none, return false}
  480.   if (FState = GettingState) then begin
  481.     if (FCurRec = FRecCount) then begin
  482.       Result := false;
  483.       FState := FinishedState;
  484.     end
  485.     else begin
  486.       Move(FBuffer[FCurRec * FRecLen], aRecord, FRecLen);
  487.       inc(FCurRec);
  488.       Result := true;
  489.     end;
  490.   end
  491.  
  492.   {if the state is "getting records with merge" return the next one in
  493.    the buffer; if there is none, try and read another buffer full from
  494.    the final merge file; if there's still none, we're finished}
  495.   else begin
  496.     if (FCurRec = FRecCount) then begin
  497.       BytesRead := TFileStream(FSrcFile).Read(FBuffer^,
  498.                                               FMaxRecCount * FRecLen);
  499.       {if there's nothing left in the final merge file, we're done}
  500.       if (BytesRead = 0) then begin
  501.         Result := false;
  502.         FState := FinishedState;
  503.         Exit;
  504.       end;
  505.       {calculate the number of records in this final buffer}
  506.       FRecCount := BytesRead div FRecLen;
  507.       FCurRec := 0;
  508.     end;
  509.     {copy the current record over}
  510.     Move(FBuffer[FCurRec * FRecLen], aRecord, FRecLen);
  511.     inc(FCurRec);
  512.     Result := true;
  513.   end;
  514. end;
  515. {--------}
  516. procedure TaaSorter.Reset;
  517. begin
  518.   {if we have merge files, close and delete them}
  519.   FF1.Free;
  520.   FF2.Free;
  521.   FG1.Free;
  522.   FG2.Free;
  523.   FF1 := nil;
  524.   FF2 := nil;
  525.   FG1 := nil;
  526.   FG2 := nil;
  527.  
  528.   {reset the object to the "waiting for records" state}
  529.   FRecCount := 0;
  530.   FState := WaitingState;
  531. end;
  532. {--------}
  533. procedure TaaSorter.srCreateMergeFiles;
  534. begin
  535.   Assert((FF1=nil) and (FF2=nil) and (FG1=nil) and (FG2=nil),
  536.          'CreateMergeFiles has been called with the mergefiles already created');
  537.   FF1 := TaaTempFileStream.Create('', fmOpenReadWrite);
  538.   TaaTempFileStream(FF1).DeleteOnDestroy := true;
  539.   FF2 := TaaTempFileStream.Create('', fmOpenReadWrite);
  540.   TaaTempFileStream(FF2).DeleteOnDestroy := true;
  541.   FG1 := TaaTempFileStream.Create('', fmOpenReadWrite);
  542.   TaaTempFileStream(FG1).DeleteOnDestroy := true;
  543.   FG2 := TaaTempFileStream.Create('', fmOpenReadWrite);
  544.   TaaTempFileStream(FG2).DeleteOnDestroy := true;
  545.   FDestFile := FF1;
  546. end;
  547. {--------}
  548. procedure TaaSorter.srGetBuffer;
  549. var
  550.   TestSize : Int64;
  551.   Size     : integer;
  552. begin
  553.   Assert(FBuffer = nil,
  554.          'GetBuffer was called with the buffer already allocated');
  555.  
  556.   {to avoid problems we'll check that the record length multiplied by
  557.    the max count does not exceed 10MB (an arbitrary value); it it is
  558.    we'll invisibly change the max record count}
  559.   TestSize := Int64(FMaxRecCount) * FRecLen;
  560.   if (TestSize <= 10 * 1024 * 1024) then
  561.     Size := TestSize
  562.   else begin
  563.     FMaxRecCount := (10 * 1024 * 1024) div FRecLen;
  564.     Size := FMaxRecCount * FRecLen;
  565.   end;
  566.  
  567.   {allocate the memory}
  568.   GetMem(FBuffer, Size);
  569.   FRecCount := 0;
  570. end;
  571. {--------}
  572. procedure TaaSorter.srMergeFiles;
  573. begin
  574.   if MergeSortFixed(TFileStream(FF1), TFileStream(FF2),
  575.                     TFileStream(FG1), TFileStream(FG2),
  576.                     FRecLen, FMaxRecCount, FCompare) then
  577.     FSrcFile := FG1
  578.   else
  579.     FSrcFile := FF1;
  580.   TFileStream(FSrcFile).Seek(0, soFromBeginning);
  581. end;
  582. {--------}
  583. procedure TaaSorter.srQuickSortBuffer;
  584. begin
  585.   Assert(FRecCount <> 0,
  586.          'calling quicksort with an empty buffer');
  587.   QuickSort(FBuffer, FRecLen, FRecCount, FCompare);
  588. end;
  589. {--------}
  590. procedure TaaSorter.srSetCompare(aValue : TaaMergeCompare);
  591. begin
  592.   {the compare function can only be set if we're not in the middle of
  593.    adding or getting records}
  594.   Assert((FState = WaitingState) or (FState = FinishedState),
  595.          'can only change the comparison function when the sorter is empty');
  596.  
  597.   FCompare := aValue;
  598. end;
  599. {--------}
  600. procedure TaaSorter.srMaxRecCount(aValue : integer);
  601. begin
  602.   {the max record count can only be set if we're not in the middle of
  603.    adding or getting records}
  604.   Assert((FState = WaitingState) or (FState = FinishedState),
  605.          'can only change the maximum record count when the sorter is empty');
  606.  
  607.   {only do something if the user is changing the value}
  608.   if (aValue <> FMaxRecCount) then begin
  609.  
  610.     {if we have allocated the buffer, free it}
  611.     if (FBuffer <> nil) then begin
  612.       FreeMem(FBuffer);
  613.       FBuffer := nil;
  614.     end;
  615.  
  616.     {set the new value}
  617.     FMaxRecCount := aValue;
  618.   end;
  619. end;
  620. {--------}
  621. procedure TaaSorter.srSetRecLen(aValue : integer);
  622. begin
  623.   {the record length can only be set if we're not in the middle of
  624.    adding or getting records}
  625.   Assert((FState = WaitingState) or (FState = FinishedState),
  626.          'can only change the record length when the sorter is empty');
  627.  
  628.   {only do something if the user is changing the value}
  629.   if (aValue <> FRecLen) then begin
  630.  
  631.     {if we have allocated the buffer, free it}
  632.     if (FBuffer <> nil) then begin
  633.       FreeMem(FBuffer);
  634.       FBuffer := nil;
  635.     end;
  636.  
  637.     {set the new value}
  638.     FRecLen := aValue;
  639.   end;
  640. end;
  641. {====================================================================}
  642.  
  643. end.
  644.